#include "model.h"
#include "objfile.h"
#include "utils.h"
#include "tgaimage.h"

#include <filesystem>
#include <iostream>
#include <fstream>
#include <cstring>
#include <vector>
#include <sstream>

namespace raster {

using Vec3f = mathengine::Vec3f;
using Vec4f = mathengine::Vec4f;
namespace fs = std::filesystem;

Model::Model(const char *filename) : verts_(), faces_() {
    std::ifstream in(filename);
    if (in.fail()) {
        char buf[512];
        int errnum = errno;
        STRERROR_R(buf, sizeof(buf), errnum);
        std::cerr << "Failed to open file: " << filename << ". reason(" << errnum << "): " << buf << std::endl;
        return;
    }

    std::string line;
    while (std::getline(in, line)) {
        std::istringstream iss(line);
        char trash;
        if (!line.compare(0, 2, "v ")) { // e.g. v -0.000581696 -0.734665 -0.623267
            float x, y, z;
            iss >> trash;
            iss >> x >> y >> z;
            verts_.push_back(Vec3f{x, y, z});
        } else if (!line.compare(0, 3, "vn ")) { // e.g. vn  -0.472 -0.641 0.606
            float x, y, z;
            iss >> trash >> trash;
            iss >> x >> y >> z;
            norms_.push_back(Vec3f{x, y, z});
        } else if (!line.compare(0, 3, "vt ")) { // e.g. vt  0.260 0.002 0.000
            float u, v;
            iss >> trash >> trash;
            iss >> u >> v;
            uv_.push_back(Vec2f{u, v});
        } else if (!line.compare(0, 2, "f ")) { // e.g. f 618/616/618 609/618/609 619/619/619
            std::vector<Vec3i> f;
            int vidx, uvidx, nidx;
            iss >> trash;
            while (iss >> vidx >> trash >> uvidx >> trash >> nidx) {
                vidx--; // in wavefront obj, all indices start at 1, not 0
                uvidx--;
                nidx--;
                f.push_back(Vec3i{vidx, uvidx, nidx});
            }
            faces_.push_back(f);
        }
    }
    std::cout << "# v# " << verts_.size() << ", #vt# " << uv_.size() << ", #vn# " << norms_.size() << ", #f# " << faces_.size() << std::endl;
    loadTexture(filename, "_diffuse.tga", diffusemap_);
    // loadTexture(filename, "_nm_tangent.tga", normalmap_);
    loadTexture(filename, "_nm.tga", normalmap_);
    loadTexture(filename, "_spec.tga", specularmap_);
}

int Model::nverst() {
    return static_cast<int>(verts_.size());
}

int Model::nfaces() {
    return static_cast<int>(faces_.size());
}

Vec3f Model::norm(int iface, int nvert) {
    int idx = faces_[iface][nvert][2];
    return Vec3f{ norms_[idx].x(), norms_[idx].y(), norms_[idx].z() };
}

Vec3f Model::norm(Vec2f uvf) {
    Vec2i st{ static_cast<int>(uvf[0] * normalmap_.width()),
              static_cast<int>(uvf[1] * normalmap_.height()) };
    TGAColor c = normalmap_.get(st[0], st[1]);
    Vec3f res;
    for (int i = 0; i < 3; i++)
        res[2 - i] = static_cast<float>(c[i]) / 255.f * 2.f - 1.f; // to make RGB vary in [-1, 1]
    return res;
}

std::vector<int> Model::face(int idx) {
    std::vector<int> f;
    // 1 face has at most 3 v/n/t, every v/n/t data stored in one Vec3f
    // faces_: {v1/uv1/n1, v2/uv2/n2, v3/uv3/n3}, {v1/uv1/n1, v2/uv2/n2, v3/uv3/n3}, ...
    for (int i = 0; i < faces_[idx].size(); i++) { // 3 vertex indices at most
        f.push_back(faces_[idx][i][0]);
    }
    return f;
}

Vec3f Model::vert(int i) {
    return verts_[i];
}

Vec3f Model::vert(int iface, int nvert) {
    return verts_[faces_[iface][nvert][0]];
}

/// @brief get uv coordinates of the nvert-th vertex of the iface-th face
/// @param iface the index of the face
/// @param nvert the index of the vertex in the face
Vec2f Model::uv(int iface, int nvert) {
    // [u, v] varies in [0, 1], texture's pixel coordinate = [u * width, v * height]
    int idx = faces_[iface][nvert][1];
    return {uv_[idx].x(), uv_[idx].y()};
}

TriangleList Model::LoadObjs() {
    fs::path basePath = PROJECT_ROOT_DIR;
    fs::path inPath = basePath.lexically_normal() / "obj";
    // fs::path filepath = inPath / "cube_v3.obj";
    fs::path filepath = inPath / "cylider.obj";

    TriangleList triangles;

    ObjFile obj(filepath.string().c_str());
    for (int i = 0; i < obj.verts.size(); i += 3)
    {
        Vertex verts[3]{
            Vertex( Vec4f({obj.verts[i].x(),     obj.verts[i].y(),     obj.verts[i].z(),     1}),
                    Vec4f({obj.norms[i].x(),     obj.norms[i].y(),     obj.norms[i].z(),     1})),
            Vertex( Vec4f({obj.verts[i + 1].x(), obj.verts[i + 1].y(), obj.verts[i + 1].z(), 1}),
                    Vec4f({obj.norms[i + 1].x(), obj.norms[i + 1].y(), obj.norms[i + 1].z(), 1})),
            Vertex( Vec4f({obj.verts[i + 2].x(), obj.verts[i + 2].y(), obj.verts[i + 2].z(), 1}),
                    Vec4f({obj.norms[i + 2].x(), obj.norms[i + 2].y(), obj.norms[i + 2].z(), 1}))
        };

        std::shared_ptr<Triangle> t(new Triangle(verts));
        triangles.push_back(t);
    }

    for (auto &t : triangles) {
        std::cout << "triangle: " << *t << std::endl;
    }

    return triangles;
}

TriangleList Model::generateSimpleTriangles() {
    TriangleList triangles;

    // test data
    mathengine::Vec3f v[3]{{100, 0, -200}, {-100, 0, -200}, {0, 100, -200}};
    std::shared_ptr<Triangle> t1(new Triangle(v));
    t1->setVertexColor(0, Color(255, 0, 0));
    t1->setVertexColor(1, Color(0, 255, 0));
    t1->setVertexColor(2, Color(0, 0, 255));

    triangles.push_back(t1);
    return triangles;
}

void Model::loadTexture(std::string filename, const char *suffix, TGAImage &image) {
    // e.g. objfile = "african_head.obj", suffix = "_diffuse.tga", 
    //  so, texfile = "african_head_diffuse.tga"

    std::string texfile(filename);
    size_t dot = texfile.find_last_of(".");
    if (dot != std::string::npos) {
        texfile = texfile.substr(0, dot) + std::string(suffix);
        bool ret = image.readTgaFile(texfile.c_str());
        std::cerr << "texture file: " << texfile << " loading " << (ret ? " ok" : "failed") << std::endl;
        // image.flipVertically();
    }
}

/// get the pixel coordinate of the diffuse texture
/// \param uvf relative coordinate of the diffuse texture, varying in [0, 1]
TGAColor Model::diffuse(Vec2f uvf) {
    Vec2i uvi{ static_cast<int>(uvf[0] * diffusemap_.width()),
               static_cast<int>(uvf[1] * diffusemap_.height()) };
    return diffusemap_.get(uvi.x(), uvi.y());
}

/// get the pixel coordinate of the specular texture
/// \param uvf relative coordinate of the specular texture, varying in [0, 1]
TGAColor Model::specular(Vec2f uvf) {
#if 1
    Vec2i uvi{ static_cast<int>(uvf[0] * specularmap_.width()),
               static_cast<int>(uvf[1] * specularmap_.height()) };
    TGAColor c = specularmap_.get(uvi[0], uvi[1]);
    return c;
#else
    float spec_intensity = c.b / 255.f; //(c.r + c.g + c.b) / 3.f;
    return spec_intensity;
    return specularmap_.get(uvi[0], uvi[1])[0] / 1.f; // each pixel store BGRA info
#endif
}

} // namespace
